feat(richtext): type richtext field value from OpenAPI spec - #684
feat(richtext): type richtext field value from OpenAPI spec#684dipankarmaikap wants to merge 10 commits into
Conversation
Expands richtext-field-value.yaml from a loose `type: object` into a
fully discriminated schema with $defs for every node and mark type,
mirroring the TypeScript types in richtext-attrs.ts.
Wires @storyblok/richtext into the openapi-codegen pipeline:
- scripts/generate.ts generates RichtextDoc, RichTextNode, RichTextMark
from the overlay spec into src/generated/
- RichtextDoc / RichTextNode / RichTextMark added to overlay.openapi.yaml
as top-level schemas and to aliases.ts so they can be included by name
- generate:openapi nx target added; existing generate target (Tiptap-based)
is unchanged
Reduces manual type maintenance in the richtext package:
- SbRichTextNode and SbRichTextMark are no longer hand-rolled unions in
types.generated.ts; they delegate to the OpenAPI-generated RichTextNode
and RichTextMark with _key, context, and common optional fields
intersected in for renderer use
- SbRichTextDoc is now RichtextDoc (the OpenAPI root type) instead of
SbRichTextNode & { type: 'doc' } which became never after the change
- SbRichTextInput extended to include SbRichTextDoc explicitly
Fixes copyWrapperTemplates in openapi-codegen to skip writing the empty
types/_sources.ts when no wrapper templates are requested.
Fixes DX-487
c74a9a7 to
7afbb95
Compare
…s.ts Make attrs required on all nodes/marks that define an attrs object, and add inner required arrays matching the non-optional fields declared in richtext-attrs.ts (LinkAttrs, ParagraphAttrs, HeadingAttrs, etc.). Also fix two structural issues surfaced by the stricter types: - NoAttrs: Record<string, never> → Record<string, unknown> to be compatible with the Record<string, unknown> that SbRichTextMark adds via intersection for attr-less marks (bold, italic, etc.) - BlokNode body items: remove additionalProperties: true whose [key: string]: unknown index signature was incompatible with SbBlokKeyDataTypes in SbBlokData
@storyblok/angular
@storyblok/astro
@storyblok/api-client
storyblok
@storyblok/eslint-config
@storyblok/js
storyblok-js-client
@storyblok/live-preview
@storyblok/management-api-client
@storyblok/migrations
@storyblok/nuxt
@storyblok/react
@storyblok/region-helper
@storyblok/richtext
@storyblok/svelte
@storyblok/vue
commit: |
…w OpenAPI types Fixes DX-487
…onent Fixes DX-487
|
pnpm v10 no longer reads the "pnpm" field in package.json. Move overrides to pnpm-workspace.yaml as required by the new config.
Add scripts/generate.ts and commit src/generated/overlay/types.gen.ts mirroring the same setup as @storyblok/richtext. Includes the generate:openapi nx target and @storyblok/openapi-codegen devDep.
…type Drop the loose hand-written interface and re-export RichtextDoc (and RichTextNode, RichTextMark) from src/generated/overlay/types.gen.ts. StoryblokRichtext is kept as a backward-compatible alias for RichtextDoc.
alexjoverm
left a comment
There was a problem hiding this comment.
@dipankarmaikap added a few minor notes, but will leave to @maoberlehner the final review.
Important note: if we squash & merge, this PR will trigger a new version on Angular, CLI, JS, Richtext and Vue SDKs. If that's intended, fine - otherwise, we need to rebase the commits and not squash when merging
| @@ -0,0 +1,29 @@ | |||
| #!/usr/bin/env -S node --experimental-strip-types --no-warnings=ExperimentalWarning | |||
There was a problem hiding this comment.
(non-blocking): are the flags in this line needed? Shouldn't it be #!/usr/bin/env tsx like in the clients/generate.ts?
| @@ -0,0 +1,7 @@ | |||
| { | |||
There was a problem hiding this comment.
@dipankarmaikap I would keep this out of the scope of this PR - we have a ticket to revisit formatting using OXC
| export type { RichtextDoc, RichTextMark, RichTextNode } from '../generated/overlay/types.gen'; | ||
|
|
||
| // Backward-compatible alias | ||
| export type { RichtextDoc as StoryblokRichtext } from '../generated/overlay/types.gen'; |
There was a problem hiding this comment.
While it's exported as an alias, the type is stricter than before - it's fine if we want to publish it like this, as it's a type fix, but let's keep in mind some users might take it as a breaking change as technically it might be.
| export type SbRichTextDoc = RichtextDoc; | ||
| export type SbRichTextTextNode = SbRichTextNode & { type: 'text' }; | ||
| export type SbRichTextInput = SbRichTextNode | SbRichTextNode[] | null | undefined; | ||
| export type SbRichTextInput = SbRichTextDoc | SbRichTextNode | SbRichTextNode[] | null | undefined; |
There was a problem hiding this comment.
The type is different than before - it's fine if we want to publish it like this, as it's a type fix, but let's keep in mind some users might take it as a breaking change as technically it might be.
| "resolutions": { | ||
| "cypress": "^14.3.3" | ||
| }, | ||
| "pnpm": { |
There was a problem hiding this comment.
(non-blocking): same here - it's not related to the PR. No issue keeping it as it's low risk, but better to tackle it in its own PR moving forward. Will give better visibility, for example, in cases where a regression is introduced
| @@ -6,3 +6,5 @@ packages: | |||
| onlyBuiltDependencies: | |||
There was a problem hiding this comment.
| $ref: ./shared/stories/field-types/plugin-field-value.yaml | ||
| RichtextFieldValue: | ||
| $ref: ./shared/stories/field-types/richtext-field-value.yaml | ||
| RichTextNode: |
There was a problem hiding this comment.
Naming of the Richtext / Rich Text field already is a mess, but at least we should not make it worse. The overlay currently uses Richtext. Let's keep it that way. And/or talk to docs and product about what the right name is (in the Storyblok UI, the field is named Richtext).
| id: | ||
| type: [string, 'null'] | ||
| description: Blok instance ID | ||
| body: |
There was a problem hiding this comment.
Should use:
body:
oneOf:
- type: array
items:
$ref: ../block-content.yaml # from field-types/ dir
- type: 'null'| properties: | ||
| id: | ||
| type: [string, 'null'] | ||
| description: Blok instance ID |
There was a problem hiding this comment.
According to docs team, we should use Block not Blok.
## Summary Extracts the `tools/openapi-codegen` changes from #684 into a standalone PR so the spec work can be reviewed and merged independently. ## Changes ### `tools/openapi-codegen` **Spec (`specs/`)** - `richtext-field-value.yaml` — expanded from a loose `type: object` into a fully discriminated schema with `$defs` for all 17 node types and 12 mark types - `BlockNode.attrs.body` items reference `../block-content.yaml` (canonical definition) instead of an inline duplicate - `overlay.openapi.yaml` — added `RichTextNode` and `RichTextMark` as top-level schema refs so they are emitted as named types; renamed `RichtextFieldValue` → `RichTextFieldValue` - `mapi/components/field-types/richtext-field.yaml` — description updated to follow naming conventions **Source (`src/`)** - `aliases.ts` — registered `RichTextDoc`, `RichTextNode`, `RichTextMark`; updated `RichtextFieldValue` → `RichTextFieldValue` - `index.ts` — fixed `copyWrapperTemplates` to skip writing the empty `types/_sources.ts` when no wrapper templates are requested - `known-types.ts`, `templates.ts` — updated `RichtextFieldValue` → `RichTextFieldValue`; registered `RichtextFieldValue` in `provides` for backward compat **Templates (`templates/`)** - `field.ts` — updated import/export/map entry for `RichTextFieldValue`; added deprecated `RichtextFieldValue` alias ## Backward compatibility `RichtextFieldValue` has been renamed to `RichTextFieldValue`. To avoid breaking consumer packages all at once, the generated `field.ts` now also exports: ```ts /** @deprecated Use RichTextFieldValue instead. Will be removed in a future major version. */ export type RichtextFieldValue = RichTextFieldValue; ``` This means packages that currently import `RichtextFieldValue` (`mapi-client`, `schema`, `capi-client`, `migrations`, etc.) will continue to compile after regeneration — TypeScript/editors will surface the `@deprecated` warning so each package can migrate at its own pace. ## Naming conventions applied | Context | Convention | Example | |---|---|---| | Type names | `RichText` (PascalCase) | `RichTextFieldValue`, `RichTextDoc` | | Sentence start | `Rich text` | `Rich text field type …` | | Mid-sentence | `rich text` | `… rich text document …` | | Wire values | unchanged | `enum: [richtext]`, `toggle-richtext` | | Node type names | `Block` not `Blok` | `BlockNode` | Fixes DX-487
Summary
Types the richtext field value in OpenAPI format and wires
@storyblok/richtextandstoryblok(CLI) into the codegen pipeline so the node/mark types are generated from the spec rather than maintained by hand.Changes
tools/openapi-codegenrichtext-field-value.yaml— expanded from a loosetype: objectinto a fully discriminated schema with$defsfor all 17 node types and 12 mark types, mirroringrichtext-attrs.tsoverlay.openapi.yaml— addedRichTextNodeandRichTextMarkas top-level schema refs so they are emitted as named typesaliases.ts— registeredRichtextDoc,RichTextNode,RichTextMarkindex.ts— fixedcopyWrapperTemplatesto skip writing the emptytypes/_sources.tswhen no wrapper templates are requestedpackages/richtextscripts/generate.ts— new OpenAPI codegen entry point (mirrorslive-preview), generatesRichtextDoc,RichTextNode,RichTextMarkintosrc/generated/src/generated/overlay/types.gen.ts— committed generated outputpackage.json— addedgenerate:openapiscript,@storyblok/openapi-codegendevDep, and nx targetsrc/static/generate/richtext-type.ts— stop generatingSbRichTextNode/SbRichTextMarkas hand-rolled unions; delegate to OpenAPI-generated types with_key,context, and common optional fields intersected insrc/static/types.ts—SbRichTextDocis nowRichtextDoc;SbRichTextInputexplicitly includesSbRichTextDocSbRichTextMark[]directly andNonNullable<SbRichTextInput>for fixture typespackages/cli(storyblok)scripts/generate.ts— new OpenAPI codegen entry point, generatesRichtextDoc,RichTextNode,RichTextMarkintosrc/generated/src/generated/overlay/types.gen.ts— committed generated outputpackage.json— addedgenerate:openapiscript,@storyblok/openapi-codegendevDep, and nx targetsrc/types/storyblok.ts— replaced the hand-writtenStoryblokRichtextinterface (loosetype: stringwithanyattrs) with a backward-compatible alias toRichtextDoc; also exportsRichTextNodeandRichTextMarkTesting
pnpm nx run @storyblok/richtext:test:types✅pnpm nx run @storyblok/richtext:test✅ (174 tests)Fixes DX-487